package  
{
	import flash.display3D.Context3D;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.VertexBuffer3D;
	import flash.geom.Vector3D;
	import flash.utils.ByteArray;
	/**
	 * ...
	 * @author Olivier Lamontagne
	 */
	public class Stage3DObjParser 
	{
		private const LINE_FEED:String = String.fromCharCode(10);
		private const SPACE:String = String.fromCharCode(32);
		private const SLASH:String = "/";
		private const VERTEX:String = "v";
		private const NORMAL:String = "vn";
		private const UV:String = "vt";
		private const INDEX_DATA:String = "f";
		
		private var mVertexDataIsZxy:Boolean = false;
		private var mMirrorUv:Boolean = false;
		private var mRandomVertexColor:Boolean = true;
		
		private var mScale:Number;
		private var mFaceIndex:uint;
		private var mVertices:Vector.<Number>;
		private var mNormals:Vector.<Number>;
		private var mUvs:Vector.<Number>;
		private var mCachedRawNormalsBuffer:Vector.<Number>;
		
		protected var mRawIndexBuffer:Vector.<uint>;
		protected var mRawPositionsBuffer:Vector.<Number>;
		protected var mRawUvBuffer:Vector.<Number>;
		protected var mRawNormalsBuffer:Vector.<Number>;
		protected var mRawColorsBuffer:Vector.<Number>;
		
		protected var mIndexBuffer:IndexBuffer3D;
		protected var mPositionsBuffer:VertexBuffer3D;
		protected var mUvBuffer:VertexBuffer3D;
		protected var mNormalsBuffer:VertexBuffer3D;
		protected var mColorsBuffer:VertexBuffer3D;
		
		private var mContext3D:Context3D;
		
		public function Stage3DObjParser(aObjFile:Class, aContext:Context3D, aScale:Number = 1, aDataIsZxy:Boolean = false, aTextureFlip:Boolean = false) 
		{
			mVertexDataIsZxy = aDataIsZxy;
			mMirrorUv = aTextureFlip;
			
			mRawColorsBuffer = new Vector.<Number>();
			mRawIndexBuffer = new Vector.<uint>();
			mRawPositionsBuffer = new Vector.<Number>();
			mRawUvBuffer = new Vector.<Number>();
			mRawNormalsBuffer = new Vector.<Number>();
			
			mScale = aScale;
			mContext3D = aContext;
			
			var definition:String = ReadClass(aObjFile);
			
			mVertices = new Vector.<Number>();
			mNormals = new Vector.<Number>();
			mUvs = new Vector.<Number>();
			
			var lines:Array = definition.split(LINE_FEED);
			var loop:uint = lines.length;
			for (var i:uint = 0; i < loop; i++)
			{
				ParseLine(lines[i]);
			}
		}
		
		public function UpdateColorsBuffer():void
		{
			if (mRawColorsBuffer.length == 0)
			{
				throw new Error("Raw colors buffer is empty");
			}
			var colorsCount:uint = mRawColorsBuffer.length / 4;
			mColorsBuffer = mContext3D.createVertexBuffer(colorsCount, 4);
			mColorsBuffer.uploadFromVector(mRawColorsBuffer, 0, colorsCount);
		}
		
		public function UpdateNormalsBuffer():void
		{
			if (mRawNormalsBuffer.length == 0)
			{
				ForceNormals();
			}
			if (mRawNormalsBuffer.length == 0)
			{
				throw new Error("Raw normals buffer is empty");
			}
			var normalsCount:uint = mRawNormalsBuffer.length / 3;
			mNormalsBuffer = mContext3D.createVertexBuffer(normalsCount, 3);
			mNormalsBuffer.uploadFromVector(mRawNormalsBuffer, 0, normalsCount);
		}
		
		public function UpdatePositionsBuffer():void
		{
			if (mRawPositionsBuffer.length == 0)
			{
				throw new Error("Raw positions buffer is empty");
			}
			var positionsCount:uint = mRawPositionsBuffer.length / 3;
			mPositionsBuffer = mContext3D.createVertexBuffer(positionsCount, 3);
			mPositionsBuffer.uploadFromVector(mRawPositionsBuffer, 0, positionsCount);
		}
		
		public function UpdateUvBuffer():void
		{
			if (mRawUvBuffer.length == 0)
			{
				throw new Error("Raw UV buffer is empty");
			}
			var uvCount:uint = mRawUvBuffer.length / 2;
			mUvBuffer = mContext3D.createVertexBuffer(uvCount, 2);
			mUvBuffer.uploadFromVector(mRawUvBuffer, 0, uvCount);
		}
		
		public function UpdateIndexBuffer():void
		{
			if (mRawIndexBuffer.length == 0)
			{
				throw new Error("Raw Index Buffer is empty");
			}
			mIndexBuffer = mContext3D.createIndexBuffer(mRawIndexBuffer.length);
			mIndexBuffer.uploadFromVector(mRawIndexBuffer, 0, mRawIndexBuffer.length);
		}
		
		public function RestoreNormals():void
		{
			mRawNormalsBuffer = mCachedRawNormalsBuffer.concat();
		}
		
		public function Get3PointNormal(aP0:Vector3D, aP1:Vector3D, aP2:Vector3D):Vector3D
		{
			var p0p1:Vector3D = aP1.subtract(aP0);
			var p0p2:Vector3D = aP2.subtract(aP0);
			var normal:Vector3D = p0p1.crossProduct(p0p2);
			normal.normalize();
			return normal;
		}
		
		public function ForceNormals():void
		{
			mCachedRawNormalsBuffer = mRawNormalsBuffer.concat();
			var i:uint;
			var index:uint;
			var loop:uint = mRawPositionsBuffer.length / 3;
			var vertices:Vector.<Vector3D> = new Vector.<Vector3D>();
			var vertex:Vector3D;
			for (i = 0; i < loop; ++i)
			{
				index = 3 * i;
				vertex = new Vector3D(mRawPositionsBuffer[index], mRawPositionsBuffer[index + 1], mRawPositionsBuffer[index + 2]);
				vertices.push(vertex);
			}
			loop = vertices.length;
			var p0:Vector3D;
			var p1:Vector3D;
			var p2:Vector3D;
			var normal:Vector3D;
			mRawNormalsBuffer = new Vector.<Number>();
			for (i = 0; i < loop; i += 3)
			{
				p0 = vertices[i];
				p1 = vertices[i + 1];
				p2 = vertices[i + 2];
				normal = Get3PointNormal(p0, p1, p2);
				mRawNormalsBuffer.push(normal.x, normal.y, normal.z);
				mRawNormalsBuffer.push(normal.x, normal.y, normal.z);
				mRawNormalsBuffer.push(normal.x, normal.y, normal.z);
			}
		}
		
		public function DataDumpTrace():void
		{
			trace(DataDumpString());
		}
		
		public function DataDumpString():String
		{
			var str:String;
			str = "//Stage3d Model Data begins\n\n";
			
			str += "private var mIndex:Vector.<uint> ";
			str += "= new Vector.<uint>([";
			str += mRawIndexBuffer.toString();
			str += "]);\n\n";
			
			str += "private var mPositions:Vector.<Number> ";
			str += "= new Vector.<Number>([";
			str += mRawPositionsBuffer.toString();
			str += "]);\n\n";
			
			str += "private var mUvs:Vector.<Number> ";
			str += "= new Vector.<Number>([";
			str += mRawUvBuffer.toString();
			str += "]);\n\n";
			
			str += "private var mNormals:Vector.<Number> ";
			str += "= new Vector.<Number>([";
			str += mRawNormalsBuffer.toString();
			str += "]);\n\n";
			
			str += "private var mColors:Vector.<Number> ";
			str += "= new Vector.<Number>([";
			str += mRawColorsBuffer.toString();
			str += "]);\n\n";
			
			str += "// Stage3d Model Data ends";
			
			return str;
		}
		
		private function ReadClass(aObjFile:Class):String
		{
			var bytes:ByteArray = new aObjFile();
			return bytes.readUTFBytes(bytes.bytesAvailable);
		}
		
		private function ParseLine(aLine:String):void
		{
			var words:Array = aLine.split(SPACE);
			
			if (words.length > 0)
			{
				var data:Array = words.slice(1);
			}
			else
			{
				return;
			}
			
			var firstWord:String = words[0];
			switch(firstWord)
			{
				case VERTEX:
				{
					ParseVertex(data);
					break;
				}
				case NORMAL:
				{
					ParseNormal(data);
					break;
				}
				case UV:
				{
					ParseUV(data);
					break;
				}
				case INDEX_DATA:
				{
					ParseIndex(data);
					break;
				}
			}
		}
		
		private function ParseVertex(aData:Array):void
		{
			if ((aData[0] == '') || (aData[0] == ' '))
			{
				aData = aData.slice(1);
			}
			
			if (mVertexDataIsZxy)
			{
				mVertices.push(Number(aData[1]) * mScale);
				mVertices.push(Number(aData[2]) * mScale);
				mVertices.push(Number(aData[0]) * mScale);
			}
			else
			{
				var loop:uint = aData.length;
				if (loop > 3)
				{
					loop = 3;
				}
				for (var i:uint = 0; i < loop; ++i)
				{
					var element:String = aData[i];
					mVertices.push(Number(element) * mScale);
				}
			}
		}
		
		private function ParseNormal(aData:Array):void
		{
			if ((aData[0] == '') || (aData[0] == ' '))
			{
				aData = aData.slice(1);
			}
			var loop:uint = aData.length;
			if (loop > 3)
			{
				loop = 3;
			}
			for (var i:uint = 0; i < loop; ++i)
			{
				var element:String = aData[i];
				if (element != null)
				{
					mNormals.push(Number(element));
				}
			}
		}
		
		private function ParseUV(aData:Array):void
		{
			if ((aData[0] == '') || (aData[0] == ' '))
			{
				aData = aData.slice(1);
			}
			var loop:uint = aData.length;
			if (loop > 2)
			{
				loop = 2;
			}
			for (var i:uint = 0; i < loop; ++i)
			{
				var element:String = aData[i];
				mUvs.push(Number(element));
			}
		}
		
		private function ParseIndex(aData:Array):void
		{
			var triplet:String;
			var subdata:Array;
			var vertexIndex:int;
			var uvIndex:int;
			var normalIndex:int;
			var index:uint;
			
			var i:uint;
			var loop:uint = aData.length;
			var startHere:uint = 0;
			while ((aData[startHere] == '') || (aData[startHere] == ' '))
			{
				startHere++;
			}
			
			loop = startHere + 3;
			
			for (i = startHere; i < loop; ++i)
			{
				triplet = aData[i];
				subdata = triplet.split(SLASH);
				vertexIndex = int(subdata[0]) - 1;
				uvIndex = int(subdata[1]) - 1;
				normalIndex = int(subdata[2]) -1;
				
				if (vertexIndex < 0) vertexIndex = 0;
				if (uvIndex < 0) uvIndex = 0;
				if (normalIndex < 0) normalIndex = 0;
				
				index = 3 * vertexIndex;
				mRawPositionsBuffer.push(mVertices[index + 0], mVertices[index + 1], mVertices[index + 2]);
				
				if (mRandomVertexColor)
				{
					mRawColorsBuffer.push(Math.random(), Math.random(), Math.random(), 1);
				}
				else
				{
					mRawColorsBuffer.push(1, 1, 1, 1);
				}
				
				if (mNormals.length)
				{
					index = 3 * normalIndex;
					mRawNormalsBuffer.push(mNormals[index + 0], mNormals[index + 1], mNormals[index + 2]);
				}
				
				index = 2 * uvIndex;
				if (mMirrorUv)
				{
					mRawUvBuffer.push(mUvs[index + 0], mUvs[index + 1]);
				}
				else
				{
					mRawUvBuffer.push(1 - mUvs[index + 0], mUvs[index + 1]);
				}
			}
			mRawIndexBuffer.push(mFaceIndex + 0, mFaceIndex + 1, mFaceIndex + 2);
			mFaceIndex += 3;
		}
		
		public function get ColorsBuffer():VertexBuffer3D
		{
			if (!mColorsBuffer)
			{
				UpdateColorsBuffer();
			}
			return mColorsBuffer;
		}
		
		public function get PositionsBuffer():VertexBuffer3D
		{
			if (!mPositionsBuffer)
			{
				UpdatePositionsBuffer();
			}
			return mPositionsBuffer;
		}
		
		public function get IndexBuffer():IndexBuffer3D
		{
			if (!mIndexBuffer)
			{
				UpdateIndexBuffer();
			}
			return mIndexBuffer;
		}
		
		public function get IndexBufferCount():int
		{
			return mRawIndexBuffer.length / 3;
		}
		
		public function get UvBuffer():VertexBuffer3D
		{
			if (!mUvBuffer)
			{
				UpdateUvBuffer();
			}
			return mUvBuffer;
		}
		
		public function get NormalsBuffer():VertexBuffer3D
		{
			if (!mNormalsBuffer)
			{
				UpdateNormalsBuffer();
			}
			return mNormalsBuffer;
		}
	}
}